Skip to content

feat(flipper): Flipper Zero integration for NFC dump import#239

Merged
codebutler merged 27 commits intomasterfrom
flipper
Feb 22, 2026
Merged

feat(flipper): Flipper Zero integration for NFC dump import#239
codebutler merged 27 commits intomasterfrom
flipper

Conversation

@codebutler
Copy link
Copy Markdown
Owner

Summary

  • New :flipper KMP module with protobuf RPC client for Flipper Zero communication over USB serial and BLE
  • Platform transports: Android USB + BLE, iOS BLE, Desktop jSerialComm, Web Serial + Web Bluetooth
  • FlipperScreen UI with file browser, connection management, and batch import
  • Global MIFARE Classic key dictionary stored in DB, used as fallback in ClassicCardReader
  • FlipperNfcParser extracts Classic keys from sector trailers during import
  • FlipperKeyDictParser imports Flipper user key dictionary files
  • Comprehensive unit and integration tests

Platform Support

Platform USB BLE
Android
iOS
Desktop
Web

New Files

  • flipper/ — entire new KMP module (RPC client, transports, protobuf, parsers)
  • FlipperScreen.kt, FlipperUiState.kt, FlipperViewModel.kt — UI layer
  • FlipperTransportFactory + 4 platform implementations — DI-wired transport creation

Modified Files

  • ClassicCardReader — global key dictionary fallback auth
  • CardKeysPersister — global key CRUD methods
  • SavedKey.sqglobal_keys table
  • Platform DI graphs — FlipperTransportFactory providers
  • HomeScreen — "Flipper Zero" menu item
  • App.kt — FlipperScreen navigation route
  • strings.xml — localized Flipper UI strings

Test plan

  • :flipper:jvmTest — all pass (varint, protobuf, RPC client, key dict parser, integration)
  • :card:classic:jvmTest — global key auth test passes
  • :app:compileKotlinJvm — builds
  • :app:desktop:compileKotlinJvm — builds
  • :app:compileKotlinWasmJs — builds
  • Manual test with real Flipper Zero hardware

🤖 Generated with Claude Code

Claude and others added 27 commits February 16, 2026 22:21
…ross all platforms

iOS:
- Replace dispatch_semaphore bridging with suspendCancellableCoroutine in
  IosCardTransceiver, IosUltralightTechnology, IosVicinityTechnology, and
  IosFeliCaTagAdapter
- Replace runBlocking in IosNfcScanner with CoroutineScope(Dispatchers.IO)
  + GCD semaphore to avoid blocking GCD's worker queue thread
- Use DESFire native protocol directly instead of ISO 7816 SELECT, which
  requires AIDs registered in Info.plist (unregistered AIDs kill the session)
- Add NFCPollingISO15693 to NFC session polling options for NFC-V support
- Fix Xcode project paths from stale farebot-app/ to app/

Desktop:
- Make NfcReaderBackend.scanLoop() a suspend function, removing runBlocking
  from PN53xReaderBackend and PcscReaderBackend
- Wrap scan coroutine in try/finally to ensure _isScanning resets on cancel
- Share a single libusb context in PN533Device instead of per-call init/exit

WebUSB:
- Remove flush-on-open which left dangling transferIn promises that consumed
  subsequent device responses
- Increase transferIn buffer from 64 to 265 bytes for full PN533 frames
- Pass atrRetries to setMaxRetries so InListPassiveTarget self-resolves
  instead of relying on client-side abort (which WebUSB can't do)

DESFire:
- Handle COMMAND_ABORTED (0xCA) status code as access control exception
Add new :flipper KMP module with protobuf RPC client that communicates
with Flipper Zero over USB serial and BLE. Supports browsing the NFC
file system, importing card dumps, and importing MIFARE Classic key
dictionaries into the app's global key store.

Platform transports:
- Android: USB Host API (CDC ACM) + BLE GATT
- iOS: Core Bluetooth BLE
- Desktop: jSerialComm USB serial
- Web: Web Serial API + Web Bluetooth API

Also adds:
- Global MIFARE Classic key dictionary (global_keys DB table)
- ClassicCardReader global key auth fallback
- FlipperScreen Compose UI with file browser
- FlipperViewModel with connect/import logic
- FlipperNfcParser Classic key extraction from sector trailers
- Comprehensive tests (unit + integration)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add @ObjCSignatureOverride to conflicting CoreBluetooth delegate
  methods (centralManager and peripheral overloads)
- Fix characteristics list fallback (use early return instead of
  emptyList<CBCharacteristic>())
- Add Flipper Zero integration section to README
- Add flipper/ to project structure in README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename _connected to connected (backing property rule)
- Rename FlipperMain.kt to CommandStatus.kt (single class naming)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hBytes

- Import ObjCSignatureOverride from kotlinx.cinterop (not kotlin.experimental)
- Use NSData.dataWithBytes() instead of NSData.create() matching existing
  NfcDataConversions.kt pattern
- Remove unnecessary memScoped wrapper from toNSData()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove CLI start_rpc_session handshake — BLE goes directly into protobuf
mode. Fix all protobuf field numbers to match official flipper.proto
(ping=5/6, storage_list=7/8, storage_read=9/10, device_info=32/33).
Fix CommandStatus enum values to match proto (was missing ERROR_DECODE,
ERROR_NOT_IMPLEMENTED, ERROR_BUSY). Add FlipperDebugLog with build
version counter. Fix import progress indicator to be indeterminate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ase class

Extract FlipperBleTransportBase with shared UUID constants, read buffering
(Channel + readBuffer), flow control parsing, and close lifecycle. Android
and iOS BLE transports now extend the base class instead of duplicating
this logic. Web transport drops its local UUID aliases (JS interop has
them hardcoded).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove FlipperDebugLog, all log() calls in IosBleSerialTransport and
FlipperRpcClient, debugLog() calls in FlipperViewModel, the DebugLogView
composable, the debugLog field from FlipperUiState, and the iOS file
writer setup in MainViewController.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…X improvements

Replace separate "Import from File" and "Flipper Zero" menu items with a
single "Import" item that opens a ModalBottomSheet showing platform-aware
options (File Browser, Flipper BLE, Flipper USB). Hoist FlipperViewModel
to enable immediate connection on transport selection. Add scrim overlay
to Flipper import progress, and snackbar feedback on import completion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Disconnect Flipper on back navigation instead of a separate button.
Show 'Flipper Zero' title during connecting state, center the spinner.
Remove Import menu item from Explore tab (Cards tab only).
Fix transparent backgrounds for bottom sheet list items.
Always show Keys menu item regardless of platform.
Pass supportedCardTypes/loadedKeyBundles to HistoryContent so imported
cards display correct status icons instead of always showing unsupported.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pper retry

Replace two text menu items (Share, Save) with a single Share icon button
in the card screen top bar that shares a .json file via platform-native
file sharing (FileProvider on Android, temp file URL on iOS, download on
web, save dialog on desktop).

Simplify Flipper disconnected screen: since the connection protocol is
already chosen from the home screen, replace USB/BLE buttons with a
single Retry button that re-attempts the same protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Chrome's requestDevice with a services filter requires the device to
advertise the service UUID in its BLE advertisement packets. Flipper
Zero advertises by name but doesn't include the serial service UUID in
its advertisement data. Switch to namePrefix: 'Flipper' filter (matching
Android/iOS behavior) and move the service UUID to optionalServices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the overflow menu item with a 'Sample Cards' entry (with Code
icon) as the last option in the import bottom sheet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add expect/actual platformShareIcon so iOS gets the native-looking
IosShare icon while other platforms keep the standard Share icon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add [FareBot BLE] prefixed console.log/error messages throughout the
Web Bluetooth JS interop for easier debugging in Chrome DevTools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show the Sample Cards import option in all builds (not just debug).
Remove Keys menu item from Explore tab since it only applies to the
Cards tab. Add Import option to Explore tab menu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Import is only relevant on the Cards tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Share files are now named like 'farebot-suica-04AB1234.json' instead
of the generic 'farebot-card.json'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…of name prefix

Match the official Flipper iOS app approach: scan for advertised service
UUIDs (0x3080-0x3083) corresponding to hardware variants (f6, black,
white, clear) instead of filtering by device name prefix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r proto spec

Tests were using wrong Main envelope field numbers (e.g. 20 for
storage_list_response instead of 8, 22 for storage_read_response
instead of 10). Fixed to match the actual Flipper protobuf definition.
Also fixed CommandStatus.ERROR_STORAGE_NOT_READY assertion (value is 5,
not 2) and updated testConnectSendsStartRpcSession since BLE doesn't
use a text handshake.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the bottom action bar (Import Selected / Import Keys buttons)
with a selection-mode TopAppBar matching the Home screen pattern:
selected files show [X] N selected [Download] in the top bar. Import
Keys moves to an overflow menu on the regular top bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… file sizes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ions

USB serial connects to Flipper's CLI mode. Must send 'start_rpc_session'
and drain the CLI response before entering protobuf RPC mode. BLE
transports go directly to protobuf mode and skip this step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Flipper's serial buffer may contain existing CLI output. Send an
empty command to trigger a fresh prompt, drain until '>: ' is seen,
then send start_rpc_session and wait for the confirming newline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dshake

Web Serial reader.read() returns variable-size chunks but the transport
was discarding all bytes beyond the requested length. Added a JS-side
buffer so excess bytes are preserved for subsequent reads. Also fixed
the USB handshake to passively wait for the CLI prompt instead of
sending an extra CR that created a stale second prompt in the buffer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codebutler codebutler merged commit 9e5e95f into master Feb 22, 2026
6 checks passed
@codebutler codebutler deleted the flipper branch February 22, 2026 05:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant